__all__ = [
  'EmptyObject', 'methodtype', 'methodwrappertype', 'functiontype', 
  'pairpattern', 'git_root', 'cpyattr', 'scombine'
]

import inspect
import re
from pathlib import Path
from collections import OrderedDict

class EmptyObject():
  def __init__(self):
    pass

# not including builtin function or method
methodtype = type(EmptyObject().__init__)
methodwrappertype = type(EmptyObject().__delattr__)  # inherited method
functiontype = type(EmptyObject.__init__)

def pairpattern(source, leftIdx, pair, *, skipList = None, escapechar = '\\'):
  '''
  Returns the index of the right part of a pattern in source, or -1 if the
  right part cannot be found. leftIdx is the index of the left part. pair
  is a tuple of (left, right, nesting). skipList is a list of pair tuple
  where symbols found are ignored, having " and ' by default.
  '''

  if skipList == None:
    skipList = [
      ("'", "'", False), ('"', '"', False)
    ]

  left, right, nesting = pair
  if source[leftIdx: leftIdx + len(left)] != left:
    raise TypeError('The index {} at the given string reads "{}" instead of "{}"'.format(leftIdx, source[leftIdx], left))

  pairs = skipList + [pair]
  symbols = []
  for cur in pairs:
    if not cur[0] in symbols:
      symbols.append(cur[0])
    if not cur[1] in symbols:
      symbols.append(cur[1])

  symbols.sort(key = lambda item: len(item), reverse = True)
  symbolStack = [pair]
  i = leftIdx + len(left)

  while i < len(source):
    curSymb = ''
    # view
    view = source[i : i + 1]
    if view == escapechar:
      i += 2
      continue

    for cur in symbols:
      view = source[i : i + len(cur)]
      if view == cur:
        curSymb = cur
        break

    if curSymb == '':
      i += 1
      continue

    lastItem = symbolStack[-1]
    if lastItem[1] == curSymb:
      symbolStack.pop()
      if not len(symbolStack):
        return i
      else:
        i += len(curSymb)
        continue

    if lastItem[2]:
      asLeftPair = None
      for cur in pairs:
        if cur[0] == curSymb:
          asLeftPair = cur
          break

      if asLeftPair:
        symbolStack.append(asLeftPair)

    i += len(curSymb)

  return -1


def git_root(path = '.'):
  '''
  Returns the git repository root of `path` where a directory '.git'
  exists. ValueError is raised if `path` is not in a git repository
  '''
  cur = Path(path).resolve()
  while cur != cur.parent:
    for diritem in cur.iterdir():
      if diritem.name == '.git' and diritem.is_dir():
        return str(cur)
    cur = cur.parent
  raise ValueError('"{}" is not in a git repository')


def cpyattr(recv, src):
  '''
  Copy attributes from `src` to `recv`. Class attributes are not copied.
  For the use of handling initiation with a same-class object.
  '''
  if not isinstance(recv, src.__class__):
    raise ValueError('Cannot copy attributes between objects of different classes')
  attrs = set(dir(src))
  for cls in inspect.getmro(src.__class__):
    attrs = attrs.difference(set(dir(cls)))
  for attr in attrs:
    setattr(recv, attr, getattr(src, attr))


def scombile(former, ladder):
  '''
  Return combinations of strings in `former` and `ladder`, where `former`
  and `ladder` are sets (or any iterable) consisting of strings.
  '''
  res = OrderedDict()
  for itmf in former:
    for itml in ladder:
      comb = '{}{}'.format(itmf, itml)
      if not comb in res:
        res[comb] = comb
  return list(res.keys())


# debug code

if __name__ == '__main__':
  pass
